﻿#region Imports
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using CommonUtilities.Enums;
using CommonUtilities.Extensions;
using CommonUtilities.Info;
using CommonUtilities.Messages;
using CommonUtilities.Properties;
using CommonUtilities.Util;
using Microsoft.VisualBasic.ApplicationServices;
#endregion

namespace CommonUtilities.Logging
{
    /// <summary>
    /// Allows recording of application logs
    /// </summary>
    public class Logger
    {
        #region Properties
        /// <summary>
        /// Backing field for &apos;AllowToAll&apos; property
        /// </summary>
        private bool allowToAll;
        /// <summary>
        /// Specifies if all logs are enabled
        /// <para>(if set to true, ignores preferences for all options and sets to true, by default)</para>
        /// </summary>
        public bool AllowToAll
        {
            get { return allowToAll; }
            private set
            {
                allowToAll = value;

                if (allowToAll)
                    LogProperties._ForEach(prop => prop.SetValue(this, true, null));
            }
        }

        /// <summary>
        /// List of properties that allows various log-modes
        /// </summary>
        public IEnumerable<PropertyInfo> LogProperties
        {
            get
            {
                return this.GetType().GetProperties()
                    ._Where(prop => prop.Name != "AllowToAll" & prop.Name.StartsWith("AllowTo"));
            }
        }

        /// <summary>
        /// Specifies if a single log-mode is selected
        /// </summary>
        public bool IsAnyLogSelected
        {
            get { return LogProperties._Where(prop => (bool)prop.GetValue(this, null))._Any(); }
        }

        /// <summary>
        /// Specifies if logging to EventLogs is allowed
        /// </summary>
        public bool AllowToEventLog { get; private set; }

        /// <summary>
        /// Specifies if logging to file is allowed
        /// </summary>
        public bool AllowToFile { get; private set; }

        /// <summary>
        /// Path of log file for &apos;File&apos; log-mode
        /// </summary>
        public string LogPath { get; private set; }

        /// <summary>
        /// Event-log name
        /// </summary>
        public string EventLogName { get; private set; }

        /// <summary>
        /// Event-source name
        /// </summary>
        public string EventSourceName { get; private set; }
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a default instance of &apos;Logger&apos; with all log-modes set to true
        /// </summary>
        private Logger()
        {
            AllowToAll = true;
        }

        /// <summary>
        /// Initializes an instance of &apos;Logger&apos; to allow setting of each log-mode
        /// </summary>
        /// <param name="allowToAll">TRUE: allows logging to all log-modes, irrespective of individual selections</param>
        /// <param name="allowToEventLog">TRUE: allows logging to EventLogs</param>
        /// <param name="allowToFile">TRUE: allows logging to specified file</param>
        /// <param name="eventSource">EventSource name</param>
        /// <param name="logPath">Path of log-file for &apos;File&apos; log-mode</param>
        public Logger(bool allowToAll, bool allowToEventLog, bool allowToFile, string eventSource = "", string logPath = "")
            : this()
        {
            AllowToAll = allowToAll;
            LogPath = logPath.IsNullOrEmpty() ? AppInfo.AssemblyLocation.GetDirectoryNameFull() + @"\ActivityLog.log" : logPath;

            if (!AllowToAll)
            {
                AllowToEventLog = allowToEventLog;
                AllowToFile = allowToFile;
            }

            // Validations for log-file
            if (AllowToFile)
            {
                if (!LogPath.ValidatePath(ePathValidation.File))
                {
                    switch (MessageBoxUtil.Show(Debugger.IsAttached, Resources.Logger_Msg_CreateLog.FormatTextFromParams(LogPath),
                        Resources.Logger_Title_CreateLog, MessageBoxButtons.YesNo, MessageBoxIcon.Question))
                    {
                        case DialogResult.Yes:
                            try { Utilities.CreateFile(LogPath); }
                            catch (Exception ex) { throw new InvalidOperationException(Resources.Logger_Msg_CreateLog_Error.FormatTextFromParams(ex.Message), ex); }
                            break;

                        case DialogResult.No:
                        default:
                            throw new InvalidOperationException(Resources.Logger_Msg_LogNotCreated);
                    }
                }
            }

            // Validations for Event-log
            if (AllowToEventLog)
            {
                EventLogName = (AppInfo.AssemblyTitle != Resources.Const_NotAvailable ? AppInfo.AssemblyTitle
                    : new AssemblyInfo(Assembly.GetCallingAssembly()).Title);
                if (EventSourceName.IsNullOrEmpty()) EventSourceName = EventLogName;

                if (!EventLog.SourceExists(EventSourceName))
                {
                    switch (MessageBoxUtil.Show(Debugger.IsAttached, Resources.Logger_Msg_CreateEventSource.FormatTextFromParams(EventLogName, EventSourceName),
                        Resources.Logger_Title_CreateEventSource, MessageBoxButtons.YesNo, MessageBoxIcon.Question))
                    {
                        case DialogResult.Yes:
                            try { EventLog.CreateEventSource(EventSourceName, EventLogName); }
                            catch (Exception ex) { throw new InvalidOperationException(Resources.Logger_Msg_EventSource_Error, ex); }
                            break;

                        case DialogResult.No:
                        default:
                            throw new InvalidOperationException(Resources.Logger_Msg_AppEventNotRegistered);
                    }
                }
            }

            // No log-mode selected
            if (!IsAnyLogSelected)
                throw new InvalidOperationException(Resources.Logger_Msg_LogNotEnabled);
        }
        #endregion

        #region Methods
        /// <summary>
        /// Allows writing to log w.r.t. selected modes
        /// </summary>
        /// <param name="logger">Logger details</param>
        /// <param name="logContents">Contents of the log</param>
        /// <param name="startTime">Start time</param>
        /// <param name="completionTime">Completion time</param>
        /// <param name="ex">Exception details on error</param>
        /// <param name="severity">Severity for log</param>
        public static void WriteToLog(Logger logger, string logContents,
            DateTime? startTime, DateTime? completionTime, Exception ex,
            EventLogEntryType severity) //params EventLogEntryType[] severity)
        {
            if (startTime.IsNull()) startTime = DateTime.Now;
            if (completionTime.IsNull()) completionTime = DateTime.Now;
            if (!ex.IsNull()) severity = EventLogEntryType.Error;

            //if (!severity.IsLinqIterable(true))
            //    severity = severity.AddItem(new EventLogEntryType()).ToArray();
            //
            //string sSeverity = "";
            //severity._OrderBy(svrt => (int)svrt)._ForEach(svrt => sSeverity += svrt + "+");
            //sSeverity = "Severity"
            //    + " : " + severity._Select(svrt => (int)svrt)._Sum().ToString()
            //    + " : " + sSeverity.TrimEnd('+');

            if (logger.AllowToFile)
                WriteToFile(logger.LogPath, logContents, severity, startTime.Value, completionTime.Value);
            if (logger.AllowToEventLog)
                WriteToEventLog(logger.EventSourceName, logContents, ex, severity); //.First()

            if (!logger.IsAnyLogSelected)
                throw new InvalidOperationException(Resources.Logger_Msg_LogNotEnabled);
        }

        /// <summary>
        /// Writes log contents to file
        /// </summary>
        /// <param name="logPath">Path for storing/writing log file</param>
        /// <param name="logContents">Contents of the log</param>
        /// <param name="severity">Severity for log</param>
        /// <param name="startTime">Start time</param>
        /// <param name="completionTime">Completion time</param>
        private static void WriteToFile(string logPath, string logContents,
            EventLogEntryType severity, DateTime startTime, DateTime completionTime)
        {
            if (logPath.ValidatePath(ePathValidation.File))
            {
                File.AppendAllText(logPath, "START TIME :: " + startTime + Utilities.cNewLine.Repeator(2)); // Header
                File.AppendAllText(logPath, "Severity : " + severity.ToString() + Utilities.cNewLine
                    + logContents.TrimEnd(Utilities.cNewLine.ToCharArray()) + Utilities.cNewLine.Repeator(2)); // Body
                File.AppendAllText(logPath, "COMPLETION TIME :: " + completionTime + Utilities.cNewLine); // Footer

                // Separator
                string separator = Resources.Logger_Separator;
                File.AppendAllText(logPath, Utilities.cNewLine + separator + Utilities.cNewLine + separator + Utilities.cNewLine.Repeator(2));
            }
        }

        /// <summary>
        /// Writes log contents to EventLog
        /// </summary>
        /// <param name="sSource">Event Source as registered on the local computer</param>
        /// <param name="logContents">Contents of the log</param>
        /// <param name="ex">Exception details on error</param>
        /// <param name="severity">Severity for log</param>
        /// <param name="eventId">Event Id</param>
        private static void WriteToEventLog(string sSource, string logContents, Exception ex, EventLogEntryType severity, int eventId = 0)
        {
            logContents = logContents.ConvertStringToLines()._Select(str => str.Trim('\t')).ConvertLinesToString();
            logContents += (ex.IsNull() ? "" : (Utilities.cNewLine.Repeator(3) + "Exception details:" + Utilities.cNewLine + ex.StackTrace
                    + (ex.InnerException.IsNull() ? "" : Utilities.cNewLine.Repeator(3) + "Inner Exception details:" + Utilities.cNewLine + ex.InnerException.StackTrace)));

            EventLog.WriteEntry(sSource, logContents, severity, eventId);
        }
        #endregion
    }
}
